﻿namespace MetaProperties.Core
{
    using System;
    using System.ComponentModel;

    using MetaProperties.Framework;

    /// <summary>
    /// Base class for implementations of sourced observable values.
    /// </summary>
    /// <typeparam name="T">The type of the observable value.</typeparam>
    public abstract class GenericBoundObservable<T> : MetaProperties.Framework.IObservable<T>, IDisposable
    {
        /// <summary>
        /// The underying observable value.
        /// </summary>
        private readonly Observable<T> observableValue = new Observable<T>();

        /// <summary>
        /// The default value.
        /// </summary>
        private T defaultValue;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged
        {
            add { this.observableValue.PropertyChanged += value; }
            remove { this.observableValue.PropertyChanged -= value; }
        }

        /// <summary>
        /// Occurs when a property value is changing.
        /// </summary>
        public event PropertyChangingEventHandler PropertyChanging
        {
            add { this.observableValue.PropertyChanging += value; }
            remove { this.observableValue.PropertyChanging -= value; }
        }

        /// <summary>
        /// Occurs when the observable value changes.
        /// </summary>
        public event ValueChangedEventHandler UntypedValueChanged
        {
            add { this.observableValue.UntypedValueChanged += value; }
            remove { this.observableValue.UntypedValueChanged -= value; }
        }

        /// <summary>
        /// Occurs when the observable value is set.
        /// </summary>
        public event ValueChangedEventHandler UntypedValueSet
        {
            add { this.observableValue.UntypedValueSet += value; }
            remove { this.observableValue.UntypedValueSet -= value; }
        }

        /// <summary>
        /// Occurs when the observable value changes.
        /// </summary>
        public event ValueChangedEventHandler<T> ValueChanged
        {
            add { this.observableValue.ValueChanged += value; }
            remove { this.observableValue.ValueChanged -= value; }
        }

        /// <summary>
        /// Occurs when the observable value is set.
        /// </summary>
        public event ValueChangedEventHandler<T> ValueSet
        {
            add { this.observableValue.ValueSet += value; }
            remove { this.observableValue.ValueSet -= value; }
        }

        /// <summary>
        /// Gets or sets the default value.
        /// </summary>
        /// <value>The default value.</value>
        public T DefaultValue
        {
            get
            {
                return this.defaultValue;
            }

            set
            {
                this.defaultValue = value;
                if (!this.IsBound)
                {
                    this.observableValue.Value = value;
                }
            }
        }

        /// <summary>
        /// Gets the untyped observable value.
        /// </summary>
        /// <value>The untyped observable value.</value>
        public object UntypedValue
        {
            get
            {
                return this.observableValue.Value;
            }
        }

        /// <summary>
        /// Gets or sets the observable value.
        /// </summary>
        /// <value>The observable value.</value>
        public T Value
        {
            get
            {
                return this.observableValue.Value;
            }

            set
            {
                this.observableValue.Value = value;
            }
        }

        /// <summary>
        /// Gets the MetaProperty for the value.
        /// </summary>
        /// <value>The MetaProperty for the value.</value>
        public IMetaProperty<T> MetaProperty
        {
            get
            {
                return this.observableValue.MetaProperty;
            }
        }

        /// <summary>
        /// Gets the observable value.
        /// </summary>
        /// <value>The observable value.</value>
        protected Observable<T> ObservableValue
        {
            get
            {
                return this.observableValue;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the item is currently bound.
        /// </summary>
        /// <value>
        ///     <c>true</c> if the source is set; otherwise, <c>false</c>.
        /// </value>
        protected abstract bool IsBound { get; }

        /// <summary>
        /// Unbinds from the source.
        /// </summary>
        public abstract void Unbind();

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Unbind();
        }
    }
}
